#include "U2ResourceBackgroundQueue.h"

#include "U2LogManager.h"
#include "U2Exception.h"
#include "U2ResourceGroupManager.h"
#include "U2ResourceManager.h"
#include "U2Root.h"


U2EG_NAMESPACE_USING


// Note, no locks are required here anymore because all of the parallelisation
// is now contained in WorkQueue - this class is entirely single-threaded
//------------------------------------------------------------------------
//-----------------------------------------------------------------------
template<> U2ResourceBackgroundQueue* U2Singleton<U2ResourceBackgroundQueue>::s_pSingleton = 0;
U2ResourceBackgroundQueue* U2ResourceBackgroundQueue::getSingletonPtr(void)
{
    return s_pSingleton;
}
U2ResourceBackgroundQueue& U2ResourceBackgroundQueue::getSingleton(void)
{  
    assert( s_pSingleton );
    return ( *s_pSingleton );  
}
//-----------------------------------------------------------------------	
//------------------------------------------------------------------------
U2ResourceBackgroundQueue::U2ResourceBackgroundQueue()
{
}
//------------------------------------------------------------------------
U2ResourceBackgroundQueue::~U2ResourceBackgroundQueue()
{
	shutdown();
}
//---------------------------------------------------------------------
void U2ResourceBackgroundQueue::initialise()
{
	WorkQueue* wq = U2Root::getSingleton().getWorkQueue();
	mWorkQueueChannel = wq->getChannel("Ogre/ResourceBGQ");
	wq->addResponseHandler(mWorkQueueChannel, this);
	wq->addRequestHandler(mWorkQueueChannel, this);
}
//---------------------------------------------------------------------
void U2ResourceBackgroundQueue::shutdown()
{
	WorkQueue* wq = U2Root::getSingleton().getWorkQueue();
	wq->abortRequestsByChannel(mWorkQueueChannel);
	wq->removeRequestHandler(mWorkQueueChannel, this);
	wq->removeResponseHandler(mWorkQueueChannel, this);
}
//------------------------------------------------------------------------
BackgroundProcessTicket U2ResourceBackgroundQueue::initialiseResourceGroup(
	const U2String& name, U2ResourceBackgroundQueue::Listener* listener)
{
#if U2_THREAD_SUPPORT
	// queue a request
	ResourceRequest req;
	req.type = RT_INITIALISE_GROUP;
	req.groupName = name;
	req.listener = listener;
	return addRequest(req);
#else
	// synchronous
	U2ResourceGroupManager::getSingleton().initialiseResourceGroup(name);
	return 0; 
#endif
}
//------------------------------------------------------------------------
BackgroundProcessTicket 
U2ResourceBackgroundQueue::initialiseAllResourceGroups( 
	U2ResourceBackgroundQueue::Listener* listener)
{
#if U2_THREAD_SUPPORT
	// queue a request
	ResourceRequest req;
	req.type = RT_INITIALISE_ALL_GROUPS;
	req.listener = listener;
	return addRequest(req);
#else
	// synchronous
	U2ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
	return 0; 
#endif
}
//------------------------------------------------------------------------
BackgroundProcessTicket U2ResourceBackgroundQueue::prepareResourceGroup(
	const U2String& name, U2ResourceBackgroundQueue::Listener* listener)
{
#if U2_THREAD_SUPPORT
	// queue a request
	ResourceRequest req;
	req.type = RT_PREPARE_GROUP;
	req.groupName = name;
	req.listener = listener;
	return addRequest(req);
#else
	// synchronous
	U2ResourceGroupManager::getSingleton().prepareResourceGroup(name);
	return 0; 
#endif
}
//------------------------------------------------------------------------
BackgroundProcessTicket U2ResourceBackgroundQueue::loadResourceGroup(
	const U2String& name, U2ResourceBackgroundQueue::Listener* listener)
{
#if U2_THREAD_SUPPORT
	// queue a request
	ResourceRequest req;
	req.type = RT_LOAD_GROUP;
	req.groupName = name;
	req.listener = listener;
	return addRequest(req);
#else
	// synchronous
	U2ResourceGroupManager::getSingleton().loadResourceGroup(name);
	return 0; 
#endif
}
//------------------------------------------------------------------------
BackgroundProcessTicket U2ResourceBackgroundQueue::prepare(
	const U2String& resType, const U2String& name, 
	const U2String& group, bool isManual, 
	U2ManualResourceLoader* loader, 
	const NameValuePairList* loadParams, 
	U2ResourceBackgroundQueue::Listener* listener)
{
#if U2_THREAD_SUPPORT
	// queue a request
	ResourceRequest req;
	req.type = RT_PREPARE_RESOURCE;
	req.resourceType = resType;
	req.resourceName = name;
	req.groupName = group;
	req.isManual = isManual;
	req.loader = loader;
	// Make instance copy of loadParams for thread independence
	req.loadParams = ( loadParams ? U2_NEW_T(NameValuePairList, MEMCATEGORY_GENERAL)( *loadParams ) : 0 );
	req.listener = listener;
	return addRequest(req);
#else
	// synchronous
	U2ResourceManager* rm = 
		U2ResourceGroupManager::getSingleton()._getResourceManager(resType);
	rm->prepare(name, group, isManual, loader, loadParams);
	return 0; 
#endif
}
//------------------------------------------------------------------------
BackgroundProcessTicket U2ResourceBackgroundQueue::load(
	const U2String& resType, const U2String& name, 
	const U2String& group, bool isManual, 
	U2ManualResourceLoader* loader, 
	const NameValuePairList* loadParams, 
	U2ResourceBackgroundQueue::Listener* listener)
{
#if U2_THREAD_SUPPORT
	// queue a request
	ResourceRequest req;
	req.type = RT_LOAD_RESOURCE;
	req.resourceType = resType;
	req.resourceName = name;
	req.groupName = group;
	req.isManual = isManual;
	req.loader = loader;
	// Make instance copy of loadParams for thread independence
	req.loadParams = ( loadParams ? U2_NEW_T(NameValuePairList, MEMCATEGORY_GENERAL)( *loadParams ) : 0 );
	req.listener = listener;
	return addRequest(req);
#else
	// synchronous
	U2ResourceManager* rm = 
		U2ResourceGroupManager::getSingleton()._getResourceManager(resType);
	rm->load(name, group, isManual, loader, loadParams);
	return 0; 
#endif
}
//---------------------------------------------------------------------
BackgroundProcessTicket U2ResourceBackgroundQueue::unload(
	const U2String& resType, const U2String& name, Listener* listener)
{
#if U2_THREAD_SUPPORT
	// queue a request
	ResourceRequest req;
	req.type = RT_UNLOAD_RESOURCE;
	req.resourceType = resType;
	req.resourceName = name;
	req.listener = listener;
	return addRequest(req);
#else
	// synchronous
	U2ResourceManager* rm = 
		U2ResourceGroupManager::getSingleton()._getResourceManager(resType);
	rm->unload(name);
	return 0; 
#endif

}
//---------------------------------------------------------------------
BackgroundProcessTicket U2ResourceBackgroundQueue::unload(
	const U2String& resType, U2ResourceHandle handle, Listener* listener)
{
#if U2_THREAD_SUPPORT
	// queue a request
	ResourceRequest req;
	req.type = RT_UNLOAD_RESOURCE;
	req.resourceType = resType;
	req.resourceHandle = handle;
	req.listener = listener;
	return addRequest(req);
#else
	// synchronous
	U2ResourceManager* rm = 
		U2ResourceGroupManager::getSingleton()._getResourceManager(resType);
	rm->unload(handle);
	return 0; 
#endif

}
//---------------------------------------------------------------------
BackgroundProcessTicket U2ResourceBackgroundQueue::unloadResourceGroup(
	const U2String& name, Listener* listener)
{
#if U2_THREAD_SUPPORT
	// queue a request
	ResourceRequest req;
	req.type = RT_UNLOAD_GROUP;
	req.groupName = name;
	req.listener = listener;
	return addRequest(req);
#else
	// synchronous
	U2ResourceGroupManager::getSingleton().unloadResourceGroup(name);
	return 0; 
#endif

}
//------------------------------------------------------------------------
bool U2ResourceBackgroundQueue::isProcessComplete(
		BackgroundProcessTicket ticket)
{
	return mOutstandingRequestSet.find(ticket) == mOutstandingRequestSet.end();
}
//------------------------------------------------------------------------
void U2ResourceBackgroundQueue::abortRequest( BackgroundProcessTicket ticket )
{
	WorkQueue* queue = U2Root::getSingleton().getWorkQueue();

	queue->abortRequest( ticket );
}
//------------------------------------------------------------------------
BackgroundProcessTicket U2ResourceBackgroundQueue::addRequest(ResourceRequest& req)
{
	WorkQueue* queue = U2Root::getSingleton().getWorkQueue();

	Any data(req);

	WorkQueue::RequestID requestID = 
		queue->addRequest(mWorkQueueChannel, (u2uint16)req.type, data);


	mOutstandingRequestSet.insert(requestID);

	return requestID;
}
//-----------------------------------------------------------------------
bool U2ResourceBackgroundQueue::canHandleRequest(const WorkQueue::Request* req, const WorkQueue* srcQ)
{
	return true;
}
//-----------------------------------------------------------------------
WorkQueue::Response* U2ResourceBackgroundQueue::handleRequest(const WorkQueue::Request* req, const WorkQueue* srcQ)
{

	ResourceRequest resreq = any_cast<ResourceRequest>(req->getData());

	if( req->getAborted() )
	{
		if( resreq.type == RT_PREPARE_RESOURCE || resreq.type == RT_LOAD_RESOURCE )
		{
			U2_DELETE_T(resreq.loadParams, NameValuePairList, MEMCATEGORY_GENERAL);
			resreq.loadParams = 0;
		}
		resreq.result.error = false;
		ResourceResponse resresp(U2ResourcePtr(), resreq);
		return U2_NEW WorkQueue::Response(req, true, Any(resresp));
	}

	U2ResourceManager* rm = 0;
	U2ResourcePtr resource;
	try
	{

		switch (resreq.type)
		{
		case RT_INITIALISE_GROUP:
			U2ResourceGroupManager::getSingleton().initialiseResourceGroup(
				resreq.groupName);
			break;
		case RT_INITIALISE_ALL_GROUPS:
			U2ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
			break;
		case RT_PREPARE_GROUP:
			U2ResourceGroupManager::getSingleton().prepareResourceGroup(
				resreq.groupName);
			break;
		case RT_LOAD_GROUP:
#if U2_THREAD_SUPPORT == 2
			U2ResourceGroupManager::getSingleton().prepareResourceGroup(
				resreq.groupName);
#else
			U2ResourceGroupManager::getSingleton().loadResourceGroup(
				resreq.groupName);
#endif
			break;
		case RT_UNLOAD_GROUP:
			U2ResourceGroupManager::getSingleton().unloadResourceGroup(
				resreq.groupName);
			break;
		case RT_PREPARE_RESOURCE:
			rm = U2ResourceGroupManager::getSingleton()._getResourceManager(
				resreq.resourceType);
			resource = rm->prepare(resreq.resourceName, resreq.groupName, resreq.isManual, 
				resreq.loader, resreq.loadParams, true);
			break;
		case RT_LOAD_RESOURCE:
			rm = U2ResourceGroupManager::getSingleton()._getResourceManager(
				resreq.resourceType);
#if U2_THREAD_SUPPORT == 2
			resource = rm->prepare(resreq.resourceName, resreq.groupName, resreq.isManual, 
				resreq.loader, resreq.loadParams, true);
#else
			resource = rm->load(resreq.resourceName, resreq.groupName, resreq.isManual, 
				resreq.loader, resreq.loadParams, true);
#endif
			break;
		case RT_UNLOAD_RESOURCE:
			rm = U2ResourceGroupManager::getSingleton()._getResourceManager(
				resreq.resourceType);
			if (resreq.resourceName.empty())
				rm->unload(resreq.resourceHandle);
			else
				rm->unload(resreq.resourceName);
			break;
		};
	}
	catch (U2Exception& e)
	{
		if( resreq.type == RT_PREPARE_RESOURCE || resreq.type == RT_LOAD_RESOURCE )
		{
			U2_DELETE_T(resreq.loadParams, NameValuePairList, MEMCATEGORY_GENERAL);
			resreq.loadParams = 0;
		}
		resreq.result.error = true;
		resreq.result.message = e.getFullDescription();

		// return error response
		ResourceResponse resresp(resource, resreq);
		return U2_NEW WorkQueue::Response(req, false, Any(resresp), e.getFullDescription());
	}


	// success
	if( resreq.type == RT_PREPARE_RESOURCE || resreq.type == RT_LOAD_RESOURCE )
	{
		U2_DELETE_T(resreq.loadParams, NameValuePairList, MEMCATEGORY_GENERAL);
		resreq.loadParams = 0;
	}
	resreq.result.error = false;
	ResourceResponse resresp(resource, resreq);
	return U2_NEW WorkQueue::Response(req, true, Any(resresp));

}
//------------------------------------------------------------------------
bool U2ResourceBackgroundQueue::canHandleResponse(const WorkQueue::Response* res, const WorkQueue* srcQ)
{
	return true;
}
//------------------------------------------------------------------------
void U2ResourceBackgroundQueue::handleResponse(const WorkQueue::Response* res, const WorkQueue* srcQ)
{
	if( res->getRequest()->getAborted() )
	{
		mOutstandingRequestSet.erase(res->getRequest()->getID());
		return ;
	}

	if (res->succeeded())
	{
		ResourceResponse resresp = any_cast<ResourceResponse>(res->getData());

		// Complete full loading in main thread if semithreading
		const ResourceRequest& req = resresp.request;
#if U2_THREAD_SUPPORT == 2
		// These load commands would have been downgraded to prepare() for the background
		if (req.type == RT_LOAD_RESOURCE)
		{
			U2ResourceManager *rm = U2ResourceGroupManager::getSingleton()
				._getResourceManager(req.resourceType);
			rm->load(req.resourceName, req.groupName, req.isManual, req.loader, req.loadParams, true);
		} 
		else if (req.type == RT_LOAD_GROUP) 
		{
			U2ResourceGroupManager::getSingleton().loadResourceGroup(req.groupName);
		}
#endif
		mOutstandingRequestSet.erase(res->getRequest()->getID());

		// Call resource listener
		if (!resresp.resource.isNull()) 
		{
			if (req.type == RT_LOAD_RESOURCE) 
			{
				resresp.resource->_fireLoadingComplete( true );
			} 
			else 
			{
				resresp.resource->_firePreparingComplete( true );
			}
		} 

		// Call queue listener
		if (req.listener)
			req.listener->operationCompleted(res->getRequest()->getID(), req.result);

	}
}
